%include{

	private $compileEngine;
	private $referenceOffset;
	private $ast;
	
	public function setCompileEngine(GeryCompileEngine $engine) {
		$this->compileEngine = $engine;
	}
	
	/** 
	 * Get the abstract syntax tree
	 *
	 * @return IASTNode The abstract syntax tree
	 */
	public function getAST() {
		return $this->ast;
	}
}

%left TK_COMMA.
%left TK_OR.
%left TK_AND.
%right TK_IS.
%left TK_EQUAL TK_NOT_EQUAL.
%left TK_GT TK_LT TK_GE TK_LE.
%nonassoc TK_DOTDOT.
%left TK_PLUS TK_MIN.
%left TK_STAR TK_SLASH TK_PERCENT.
%right TK_NOT.
%left TK_DOT.
%left TK_ELSEIF.
%left TK_ELSE.
%left TK_END.

%syntax_error {
	
	if ($yymajor) {
		if ($yymajor == TK_SMURK) {
			$value = $yyminor->yy0->value;
			if ($value > 20) {
				$value = substr($value, 0, 20) . '...';
			}
				
			$tokenName = 'TK_SMURK with value [' . $value . ']';
		} else {
			$tokenName = $this->getSymbolName($yymajor);
		}
		
		$msg = "Unexpected token " . $tokenName . " on line " . $this->compileEngine->getLine();
	} else {
		$msg = "Unexpected end of file";
	}
	
	// get expected tokens
	$expected = array();
	
	$stateno = $this->stack->getTop()->stateno;
	for ($x = 0; $x < self::YYERRORSYMBOL; $x++) {
		$yyact = $this->yy_find_shift_action($x);
		if ($yyact != self::YY_ERROR_ACTION) {
			if (isset($alias[$x])) {
				$name = "'" . $alias[$x] . "'";
			} else {
				$name = $this->getSymbolName($x);
			}
			
			$expected[] = $name;
		}
	}
	
	// build msg
	$msg .= "\n";
	$msg .= "Was expecting one of:";
	foreach ($expected as $token) {
		$msg .= "\n\t" . $token;
	}
	
	// throw the exception
	throw new GeryParseErrorException($msg);
}

%name Parser


input ::= top_block_list(A). { 
	$this->ast = A;
}

top_block_list(X) ::= block_list(A). 	{ X = A; }

block_list(X) ::= block_list(L) block(A). { X = L; X->addNode(A); }
block_list(X) ::= . /* empty */ 		{ X = new ASTBlock(); }

block(X) ::= TK_SMURK(A).				{ X = new ASTSmurk(A->value); }
block(X) ::= escapes(E) reference(A).	{ X = A; X->escapes = E; }
block(X) ::= escapes(E) directive(A).	{ X = A; X->escapes = E; }
block(X) ::= interpolated(A).			{ X = A; }

/* References */
reference(X) ::= open_reference(A). 	{ X = A; }
reference(X) ::= braced_reference(A). 	{ X = A; }

simple_reference(X) ::= TK_DOLLAR(A) silent(S) TK_ID(N).
{
	// calculate smurk offset
	$start = A->offset;
	$length = (N->offset + strlen(N->value)) - $start;
	
	X = new ASTReference();
	X->name = N->value;
	X->silent = S;
	X->modifiers = array(); // no modifiers
	X->smurk = $this->compileEngine->getInputSubstring($start, $length);
}

open_reference(X) ::= TK_DOLLAR(A) silent(S) TK_ID(N) { $this->referenceOffset = N->offset + strlen(N->value); } modifier_list(M). 
{ 
	$start = A->offset;
	$length = $this->referenceOffset - $start;
	
	X = new ASTReference();
	X->name = N->value;
	X->silent = S;
	X->modifiers = M;
	X->smurk = $this->compileEngine->getInputSubstring($start, $length);
}

braced_reference(X) ::= TK_DOLLAR(A) silent(S) TK_LBRACE TK_ID(N) modifier_list(M) TK_RBRACE(R). 
{ 	
	// calculate smurk offset
	$start = A->offset;
	$length = (R->offset + 1) - $start;
	
	X = new ASTReference();
	X->name = N->value;
	X->silent = S;
	X->modifiers = M;
	X->smurk = $this->compileEngine->getInputSubstring($start, $length);
}

modifier_list(X) ::= modifier_list(L) TK_DOT modifier(M). { L[] = M; X = L; }
modifier_list(X) ::= modifier(M). { X = array(M); }
modifier_list(X) ::= . { X = array(); } /* empty */

modifier(X) ::= TK_ID(A).
{
	$this->referenceOffset = A->offset + strlen(A->value);
	
	X = new ASTModifier();
	X->name = A->value;
}

modifier(X) ::= TK_ID(A) TK_LPAREN parameter_list(P) TK_RPAREN(R).
{
	$this->referenceOffset = R->offset + 1;
	
	X = new ASTModifier(); 
	X->name = A->value;
	X->parameters = P;
}

parameter_list(X) ::= parameter_list(M) TK_COMMA expr(A). { M[] = A; X = M; }
parameter_list(X) ::= expr(A). { X = array(A); }
parameter_list(X) ::= . { X = array(); } /* empty */

// silent operator '!' for references
silent(X) ::= . /* empty */ 	{ X = false; }
silent(X) ::= TK_SILENT.		{ X = true; }

// escapes
escapes(X) ::= TK_ESCAPES(E). 	{ X = strlen(E->value); }
escapes(X) ::= . /* empty */ 	{ X = 0; }

/* Expressions */
expr(X) ::= expr(A) TK_PLUS expr(B).		{ X = new ASTAdd(A, B); }
expr(X) ::= expr(A) TK_MIN expr(B).			{ X = new ASTSub(A, B); }
expr(X) ::= expr(A) TK_STAR expr(B).		{ X = new ASTMultiply(A, B); }
expr(X) ::= expr(A) TK_SLASH expr(B).		{ X = new ASTDivide(A, B); }
expr(X) ::= expr(A) TK_PERCENT expr(B).		{ X = new ASTModulus(A, B); }
expr(X) ::= expr(A) TK_GT expr(B).			{ X = new ASTGreaterThan(A, B); }
expr(X) ::= expr(A) TK_LT expr(B).			{ X = new ASTLessThan(A, B); }
expr(X) ::= expr(A) TK_GE expr(B).			{ X = new ASTGreaterOrEqual(A, B); }
expr(X) ::= expr(A) TK_LE expr(B).			{ X = new ASTLessOrEqual(A, B); }
expr(X) ::= expr(A) TK_EQUAL expr(B).		{ X = new ASTEqual(A, B); }
expr(X) ::= expr(A) TK_NOT_EQUAL expr(B).	{ X = new ASTNotEqual(A, B); }
expr(X) ::= expr(A) TK_OR expr(B).			{ X = new ASTLogicalOr(A, B); }
expr(X) ::= expr(A) TK_AND expr(B).			{ X = new ASTLogicalAnd(A, B); }
expr(X) ::= TK_NOT expr(A).					{ X = new ASTNot(A); }
expr(X) ::= TK_LPAREN expr(A) TK_RPAREN.	{ X = new ASTParenthesis(A); }
expr(X) ::= scalar(A).						{ X = A; }
expr(X) ::= open_reference(A).				{ X = A; }
expr(X) ::= array(A).						{ X = A; }
expr(X) ::= macro_call(A).					{ X = A; }
expr(X) ::= expr(A) TK_DOT modifier_list(R). { X = new ASTScalarObject(A, R); }

/* Primitives */
scalar(X) ::= TK_STRING(A).	{ X = new ASTLiteral(A->value); }
scalar(X) ::= TK_NUMBER(A).	{ X = new ASTNumber(A->value); }

ref_or_number (X) ::= open_reference(A). { X = A; }
ref_or_number(X) ::= TK_NUMBER(A). { X = new ASTNumber(A->value); }

ref_or_string(X) ::= open_reference(A). { X = A; }
ref_or_string(X) ::= TK_STRING(A). { X = new ASTLiteral(A->value); }

/* Arrays */
array(X) ::= TK_LBRACKET array_values(P) TK_RBRACKET. 
{
	X = new ASTArray();
	X->elements = P;
}

array_values(X) ::= parameter_list(P). { X = P; }
array_values(X) ::= ref_or_number(A) TK_DOTDOT ref_or_number(B).
{
	X = new ASTArrayRange();
	X->low = A;
	X->high = B;
}

/* If, Else and Elseif Directives */
directive(X) ::= TK_IF TK_LPAREN expr(E) TK_RPAREN block_list(L) elseif_list(A) else_single(B) TK_END.
{	
	X = new ASTIfDirective(E, L, null); 
	
	if (A !== null) {
		X->setFalseBlock(A);
		A->setFalseBlock(B);
		$last = A;
	} else {
		X->setFalseBlock(B);
	}
}					

elseif_list(X) ::= elseif_list(L) TK_ELSEIF TK_LPAREN expr(E) TK_RPAREN block_list(B). 
{
	X = new ASTElseIfDirective(E, B, null);
	X->setFalseBlock(L);
}

elseif_list(X) ::= . /* empty */ { X = null; }

else_single(X) ::= TK_ELSE block_list(L). { X = new ASTElseDirective(L); }
else_single(X) ::= . /* empty */ { X = null; }


/* Set directive */
directive(X) ::= TK_SET TK_LPAREN open_reference(A) TK_IS expr(E) TK_RPAREN. 
{ 
	X = new ASTSetDirective(A, E); 
}

/* Foreach directive */
directive(X) ::= TK_FOREACH(F) TK_LPAREN simple_reference(V) TK_IN expr(A) TK_RPAREN block_list(B) else_single(E) TK_END.
{ 
	X = new ASTForeachDirective(A, V, B, E, F->line); 
}

/* Stop directive */
directive(X) ::= TK_STOP. { X = new ASTStopDirective(); }

/* Include directive */
directive(X) ::= TK_INCLUDE(P) TK_LPAREN ref_or_string(S) TK_RPAREN. {
	X = new ASTIncludeDirective();
	X->resource = S;
	X->line = P->line;
}

/* Parse directive */
directive(X) ::= TK_PARSE(P) TK_LPAREN ref_or_string(S) TK_RPAREN. { 
	X = new ASTParseDirective();
	X->resource = S;
	X->line = P->line;
}

/* Macro calls */
directive(X) ::= macro_call(A). { X = A; }

macro_call(X) ::= TK_DIRECTIVE(N) TK_LPAREN parameter_list(P) TK_RPAREN(R). 
{
	$start = N->offset;
	$length = (R->offset + 1) - $start;
	
	X = new ASTMacroCall();
	X->parameters = P;
	X->name = N->value;
	X->smurk = $this->compileEngine->getInputSubstring($start, $length);
}

directive(X) ::= macro_def(A). { X = A; }

macro_def(X) ::= TK_MACRO TK_LPAREN TK_ID(N) { 
	// register macro so that the lexer knows this macro is defined
	$this->compileEngine->registerMacro(N->value); 
} macro_parameter_list(P) TK_RPAREN block_list(B) TK_END. 
{
	X = new ASTMacro();
	X->name = N->value;
	X->parameters = P;
	X->block = B;
}

macro_parameter_list(X) ::= macro_parameter_list(M) simple_reference(A). { M[] = A; X = M; }
macro_parameter_list(X) ::= . { X = array(); } /* empty */

/* Interpolation */
interpolated(X) ::= TK_INTERPOLATE TK_LBRACE expr(E) TK_RBRACE. {
	X = new ASTInterpolation();
	X->expression = E;
}